﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Operators;
using Weazel.Badger.Vhdl.Expressions;

#endregion

namespace Weazel.Badger.Vhdl.Expressions
{
  public class BinaryExpression : Expression
  {
    private BinaryTypeResolver resolver;

		/// <summary>
		/// Left child of this expression
		/// </summary>
    private Expression left;
    public Expression Left
    {
      get
      {
        return left;
      }
    }

		/// <summary>
		/// Right child of this expression
		/// </summary>
    private Expression right;
    public Expression Right
    {
      get
      {
        return right;
      }
    }

		/// <summary>
		/// The binary operator associated with this expression
		/// </summary>
    private BinaryFunction @operator;
    public BinaryFunction Operator
    {
      get
      {
        return @operator;
      }

      set
      {
        @operator = value;
      }
    }

		/// <summary>
		/// </summary>
		/// <param name="messages"></param>
		/// <returns></returns>
    public override bool VerifyType(ref List<VerifyMessage> messages)
    {
      left.VerifyType(ref messages);
      right.VerifyType(ref messages);


      if (this.Type == Types.Types.Undefined)
      {
        messages.Add(new VerifyMessage("no type set for binary expression"));
        return false;
      }

      return true;
    }

		public override void SetActualType(Entity entity, Process process, Statements.StatementCollection statements, Vhdl.Types.Type type)
    {
      if (resolved)
      {
        if (this.Type != type)
          throw new Exception();

        return;
      }

      System.Diagnostics.Debug.Assert(resolver.ComputePossibleResultTypes().Contains(type));

      BinaryConversionResult r = resolver.GetConversions(type.TypeId);

      // test that "ParentContainer" can be safely casted to StatementCollection type
      if (ParentContainer == null)
        throw new NullReferenceException("ParentContainer not set");

      if (!(ParentContainer is Statements.StatementCollection))
      {
        string message = 
          string.Format(
            "ParentContainer of type '{0}' cannot be cast to type 'StatementCollection'", 
            ParentContainer.GetType()
          );

        throw new Exception(message);
      }

      left = 
				ConversionBuilder.ApplyConversion(
					r.LeftConversions, 
					(Statements.StatementCollection) ParentContainer, 
					entity, 
					process,
					left);

			right = 
				ConversionBuilder.ApplyConversion(
					r.RightConversions,
					(Statements.StatementCollection)ParentContainer, 					
					entity,
					process,
					right
				);

			left.SetActualType(entity, process, statements, Types.Types.CreateType(r.Map.Left, type.Width, false));
			right.SetActualType(entity, process, statements, Types.Types.CreateType(r.Map.Right, type.Width, false));

      this.Type = type;

      resolved = true;
    }

    public override TypeSet GetPossibleTypes()
    {
			BinaryTypeMap[] maps = GezelType == null || !GezelType.Signed ? @operator.UnsignedTypeMaps : @operator.SignedTypeMaps;

      TypeSet leftSet = left.GetPossibleTypes();

      if (leftSet.IsEmpty())
        throw new TypeResolveException("left type set is empty");

      TypeSet rightSet = right.GetPossibleTypes();

      if (rightSet.IsEmpty())
        throw new TypeResolveException("right type set is empty");

      resolver = 
        new BinaryTypeResolver(
          left,
          right,
          this,
          maps
        );

      if (resolver.ComputePossibleResultTypes().IsEmpty())
        throw new TypeResolveException("No possible types found for binary expression");

      return resolver.ComputePossibleResultTypes();
    }

    private void postInitialize()
    {
      if (@operator == null)
        throw new NullReferenceException("binary expression: operator cannot be null");

      if (left == null)
        throw new NullReferenceException("binary expression: left expression cannot be null");

      if (right == null)
        throw new NullReferenceException("binary expression: right expression cannot be null");
    }

    public BinaryExpression(
			IComponent parentContainer,
			BinaryFunction @operator, 
			Expression left, 
			Expression right, 
			Types.Type type, 
			GezelType gezelType
		) 
			: this(parentContainer, @operator, left, right, type, gezelType, null)
    {
      // empty
    }

    /// <summary>
    /// Create a new binary expression.
    /// </summary>
    /// <param name="operator">A binary operator</param>
    /// <param name="left">The left expression tree</param>
    /// <param name="right">The right expression tree</param>
    /// <param name="type">The type of this expression</param>
    /// <param name="expr">A gezel reference expression</param>
    public BinaryExpression(
			IComponent parentContainer,
      BinaryFunction @operator, 
      Expression left, 
      Expression right, 
      Types.Type type, 
      GezelType gezelType,
      Gezel.Model.Expression expr
    ) :
      base(parentContainer, type, gezelType)
    {
      this.@operator = @operator;
      this.left = left;      
      this.right = right;
      
      postInitialize();
    }

    /// <summary>
    /// Returns a string respresentation of this binary expression
    /// </summary>
    /// <returns>string representation</returns>
    public override string ToString()
    {
      return @operator.ToString(left.ToString(), right.ToString(), Type.Width);
    }
  }
}
